package main;

import gui.MainWindow;
import gui.threed.LabyrinthUniverse;

import java.awt.event.KeyListener;
import java.util.ArrayList;

import labyrinth.Labyrinth;
import agent.HumanAgent;
import agent.IAgent;
import agent.SimpleAgent;
import agent.moves.IAgentMove;

/**
 * The main class of the programm
 * 
 * @author Johannes Kulick
 * @version 0.1
 * @licence GNU Lesser Genaral Public Licence
 */
public class LabyrinthControl {

	public static final int NORMAL = 0;
	public static final int HARD = 1;
	
	private Labyrinth mLabyrinth;
	private LabyrinthUniverse mLabUniverse;
	private LabyrinthLoop mMainLoop;
	private MainWindow mMainWindow;
	private ArrayList<IAgent> mAgents = new ArrayList<IAgent>();
	private ArrayList<IAgentListener> mListeners = new ArrayList<IAgentListener>();
	private int mDifficulty;


	public static void main(String[] args) {
		new LabyrinthControl();
	}

	public LabyrinthControl() {
		
		// Create the main window
		mMainWindow = new MainWindow("Robot Labyrinth", this);
		
		mDifficulty = HARD;
		
		// Always have a SimpleAgent
		registerAgent(new SimpleAgent());
		registerAgent(new HumanAgent());
		
		mMainWindow.updateAgentList();

		mLabUniverse = new LabyrinthUniverse(mMainWindow.getLabArea());
		newLabyrinth();

		// Resize main window to the right size and show it
		mMainWindow.pack();
		mMainWindow.setVisible(true);
	}

	/**
	 * Starts the main loop which asks the agent for its moves
	 * 
	 * @param agent
	 *            The agent which moves through the labyrinth
	 */
	public void startLoop(IAgent agent) {
		if (mMainLoop != null) {
			mMainLoop.interrupt();
			mLabUniverse.reset();
		}
		mLabUniverse.setAgent(agent);
		mMainLoop = new LabyrinthLoop(agent, this, mLabyrinth);
		mMainLoop.start();
		for (IAgentListener l : mListeners) {
			l.started();
		}
	}

	/**
	 * Stops the main loop
	 */
	public void stopLoop() {
		if (mMainLoop != null) {
			mMainLoop.interrupt();
			for (IAgentListener l : mListeners) {
				l.stopped();
			}
		}
	}

	/**
	 * Moves the agent the specified move
	 * 
	 * @param nextMove
	 *            The move
	 * @throws InterruptedException
	 *             Internal use
	 */
	public void move(IAgentMove nextMove) throws InterruptedException {
		mLabUniverse.moveAgent(nextMove);
		for (IAgentListener l : mListeners) {
			l.incrementSteps();
		}
	}

	/**
	 * Called on arrival of the agent
	 */
	public void arrived() {
		for (IAgentListener l : mListeners) {
			l.arrived();
		}
	}

	/**
	 * Creates a new random labyrinth
	 */
	public void newLabyrinth() {
		// Break agent pathfinding, on new labyrinth creation
		if (mMainLoop != null) {
			mMainLoop.interrupt();
			mLabUniverse.reset();
			for (IAgentListener l : mListeners) {
				l.stopped();
			}
		}

		// A test labyrinth
		int labmatrix[][] = new int[25][25];
		labmatrix = Labyrinth.createLabyrinth(labmatrix, 3, 6, 16, 75);
		labmatrix = Labyrinth.createLabyrinth(labmatrix, 3, 5, 8, 125);
		labmatrix = Labyrinth.createLabyrinth(labmatrix, 2, 4, 4, 350);
		labmatrix = Labyrinth.createLabyrinth(labmatrix, 1, 3, 2, 200);

		// Create a virtual universe and show it
		mLabyrinth = new Labyrinth(labmatrix);
		mLabUniverse.showLabyrinth(mLabyrinth);

	}
	/**
	 * Registers a new Agent-Object to be a possible agent
	 * @param agent The agent to register
	 */
	public void registerAgent(IAgent agent) {
		mAgents.add(agent);
		if(agent.isHuman()) mMainWindow.getLabArea().addKeyListener((KeyListener) agent);
	}

	/**
	 * Unregister an agent
	 * @param agent
	 */
	public void unregisterAgent(IAgent agent) {
		mAgents.remove(agent);
	}
	
	/**
	 * Gives all possible agents
	 * @return An ArrayList<IAgent> of all possible Agents
	 */
	public ArrayList<IAgent> getPossibleAgents() {
		return mAgents;
	}

	/**
	 * Register an AgentListener, to get status messages.
	 * @param listener
	 */
	public void registerListener(IAgentListener listener) {
		mListeners.add(listener);
	}
	
	/**
	 * Unregister an AgentListener
	 * @param listener
	 */
	public void unregisterListener(IAgentListener listener) {
		mListeners.remove(listener);
	}

	public int getDifficulty() {
		return mDifficulty;
	}
}
